/*
	Copyright (C) 2010 2011 Rémi Thebault

	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include <QPen>
#include <QColor>
#include <QGraphicsSceneWheelEvent>
#include <QGraphicsSceneMouseEvent>
#include <QKeyEvent>

#include "ganttscene.h"
#include "ganttmtask.h"
#include "ganttatask.h"
#include "ganttcycle.h"
#include "ganttview.h"
#include "viewmanager.h"
#include "ganttlink.h"
#include "ganttgrads.h"
#include "procycle.h"
#include "taskviewhelper.h"
#include "timeaxis.h"


#include <procycle/cycle.h>



GanttScene::GanttScene(ProCycle* app, QRectF rect, QObject *parent) :
	QGraphicsScene(rect, parent),
	PCChild (app),
    m_cycle(app->activeCycle())
{
    m_grads = new GanttGrads(app);
    addItem(m_grads);
    m_grads->setPos(0, 0);

	m_ganttCycle = new GanttCycle(app, m_cycle);
	addItem(m_ganttCycle);
    m_ganttCycle->setPos(0, 0);
	m_ganttCycle->replaceChildren();

    m_cycleTime = addLine(QLine(0, 0, 0, 0), QPen(Qt::red));
	m_cycleTime->setZValue(1);
}



void GanttScene::initSizePolicy() {
    QRectF r = sceneRect();
    m_preferredSize = QSize(r.width(), r.height());
}



void GanttScene::resetCycle(PC::Cycle *cycle) {
	m_cycle = cycle;
    m_ganttCycle->resetTask(m_cycle);

    widthResize();
    heightResize();
}


TimeAxis *GanttScene::axis() const {
    return app()->axis();
}


GanttTask* GanttScene::createGTask(ProCycle *app, PC::Task *task, GanttCycle* gcycle, GanttMTask *parent) {
	if (PC::AtomTask* at = dynamic_cast<PC::AtomTask*>(task)) {
		return new GanttATask(app, at, gcycle, parent);
	}
	if (PC::MacroTask* mt = dynamic_cast<PC::MacroTask*>(task)) {
		return new GanttMTask(app, mt, gcycle, parent);
	}
	return 0;
}



GanttTask* GanttScene::resolvTask(QString tbsId) {
	QString cTbsId = m_cycle->tbsId();
	if (!tbsId.startsWith(cTbsId)) {
		return 0;
	}
	if (tbsId.length() == cTbsId.length()) {
		return m_ganttCycle;
	}
	tbsId = tbsId.right(tbsId.length()-cTbsId.length()-1);
	return m_ganttCycle->resolvTask(tbsId);
}


GanttTask* GanttScene::taskAt(const QPointF &p) {
	QGraphicsItem* i = itemAt(p);
	if (GanttTask* gt = dynamic_cast<GanttTask*>(i)) {
		return gt;
	}
	return 0;
}



void GanttScene::editModeChanged() {
	m_ganttCycle->editModeChanged();
	update();
}




void GanttScene::processExpansionChange(PC::Task* task) {
	m_ganttCycle->replaceChildren();
    m_ganttCycle->processExpansionChange(task);

    heightResize();
}



void GanttScene::processTaskTimeChanged(PC::Task* task) {
	GanttTask* gt = resolvTask(task->tbsId());
	if (gt) {
		gt->update(gt->boundingRect());
        int ct = axis()->unit2px(m_cycle->length());
		m_cycleTime->setLine(QLine(ct, 0, ct, height()));
	}
    widthResize();
}


void GanttScene::processTaskValueChanged(PC::Task* task, PC::Task::Data /*data*/) {
	GanttTask* gt = resolvTask(task->tbsId());
	gt->update(gt->boundingRect());

    widthResize();
}


void GanttScene::processTaskCreated(PC::MacroTask *parent, PC::Task *task) {
	GanttMTask* gp = static_cast<GanttMTask*>(resolvTask(parent->tbsId()));
	GanttTask* gt = createGTask(app(), task, m_ganttCycle, gp);
	gp->insertTask(gt, task->pos());
    m_ganttCycle->replaceChildren();

    heightResize();
}


void GanttScene::processTaskWillDelete(PC::Task* task) {
	GanttTask* gt = resolvTask(task->tbsId());
	GanttMTask* gp = static_cast<GanttMTask*>(resolvTask(task->parent()->tbsId()));
	gp->removeTask(gt);
	removeItem(gt);
	delete gt;
    m_ganttCycle->replaceChildren();

    heightResize();
}


void GanttScene::processTaskTransformed(PC::MacroTask *parent, int taskPos) {
	GanttMTask* gmp = static_cast<GanttMTask*>(resolvTask(parent->tbsId()));
	GanttTask* gt = resolvTask(PC::Task::makeTbsId(parent, taskPos));
	PC::Task* task = parent->task(taskPos);
	gmp->removeTask(gt);
	removeItem(gt);
	delete gt;

	gt = createGTask(app(), task, m_ganttCycle, gmp);
	gmp->insertTask(gt, taskPos);

	m_ganttCycle->replaceChildren();
}

void GanttScene::processTaskMoved(PC::MacroTask *parentTo, PC::Task *parentFrom, int posTo, int posFrom) {
	GanttMTask* gpFrom = static_cast<GanttMTask*>(resolvTask(parentFrom->tbsId()));
	GanttMTask* gpTo = static_cast<GanttMTask*>(resolvTask(parentTo->tbsId()));

	GanttTask* gt = resolvTask(PC::Task::makeTbsId(parentFrom, posFrom));

	gpFrom->removeTask(gt);
	gpTo->addTask(gt);

	m_ganttCycle->replaceChildren();
}

void GanttScene::processLinkCreated(PC::Link *link) {
	GanttLink* gl = new GanttLink(app(), m_ganttCycle, link);
	m_ganttCycle->addLink(gl);
	gl->setPos(0, 0);
	update();
}

void GanttScene::processLinkWillDelete(PC::Link *link) {
	GanttLink* gl = m_ganttCycle->findGLink(link);
	if (gl) {
		m_ganttCycle->removeLink(gl);
		removeItem(gl);
		delete gl;
	}
}


void GanttScene::processScheduled() {
	m_ganttCycle->processScheduled();
}



void GanttScene::selectTask(PC::Task *selected, PC::Task* unselected) {
	GanttTask* gt;
	if (unselected) {
		if (gt = resolvTask(unselected->tbsId())) {
			gt->setSelected(false);
		}
	}
	if (selected) {
		if (gt = resolvTask(selected->tbsId())) {
			gt->setSelected(true);
		}
	}
}



void GanttScene::selectLink(PC::Link *selected, PC::Link* unselected) {
	GanttLink* gl;
	if (unselected) {
		if(gl = m_ganttCycle->findGLink(unselected)) {
			gl->setSelected(false);
		}
	}
	if (selected) {
		if(gl = m_ganttCycle->findGLink(selected)) {
			gl->setSelected(true);
		}
	}
}



void GanttScene::toggleCriticalPath(bool on) {
	m_ganttCycle->setCycleProp(PC::Cycle::PROP_DRAWCRITICAL, QVariant(on));
	update();
}


void GanttScene::toggleCycleOverlap(bool on) {
	m_ganttCycle->setCycleProp(PC::Cycle::PROP_DRAWOVERLAP, QVariant(on));
	update();
}


void GanttScene::toggleTasksFloat(bool on) {
	m_ganttCycle->setCycleProp(PC::Cycle::PROP_DRAWFLOATS, QVariant(on));
	update();
}


void GanttScene::toggleTaskName(bool on) {
    m_ganttCycle->setCycleProp(PC::Cycle::PROP_DRAWTEXT, QVariant(on));
    update();
}



const float zoomUnit = 1.2f;


void GanttScene::zoomFit() {
    m_preferredSize.setWidth(app()->ganttView()->viewport()->width());

    float scale = m_preferredSize.width() / (1.1 * m_cycle->length());
    axis()->setScale(scale);
    app()->viewManager()->scrollHorizontal(0);
    app()->viewManager()->resizeScene(m_preferredSize);

    int ct = axis()->unit2px(m_cycle->length());
    m_cycleTime->setLine(QLine(ct, 0, ct, height()));

    update();

}



void GanttScene::zoomIn() {
    axis()->setScale(axis()->scale() * zoomUnit);
    widthResize();
}

void GanttScene::zoomOut() {
    axis()->setScale(axis()->scale() / zoomUnit);
    widthResize();
}


void GanttScene::toggleZoomWindow(bool on) {
    m_windowZoomMode = on;
    app()->ganttView()->setCursor(on ? Qt::SplitHCursor: Qt::ArrowCursor);
}


void GanttScene::wheelEvent(QGraphicsSceneWheelEvent *event) {
    if (event->modifiers() == Qt::ControlModifier &&
            event->orientation() == Qt::Vertical) {

        float delta = event->delta() / 120.f; // delta is eights of deg, most mouse have step = 15deg (aka 120)
        axis()->setScale(axis()->scale() * std::pow(zoomUnit, delta));

        widthResize();

        event->accept();
    }
    else {
        QGraphicsScene::wheelEvent(event);
    }
}


void GanttScene::mousePressEvent(QGraphicsSceneMouseEvent *event) {
    if (m_windowZoomMode) {

        m_windowZoomCancelled = false;

        m_windowZoomStart = event->scenePos().x();
        QLineF line = QLineF(m_windowZoomStart, 0,
                             m_windowZoomStart, height());
        QPen p = QPen(Qt::blue);
        QBrush b = QBrush(QColor(200, 200, 200, 128));

        m_windowZoomLine1 = addLine(line, p);
        m_windowZoomLine2 = addLine(line, p);
        m_windowZoomRect1 = addRect(0, 0, m_windowZoomStart, height(), QPen(), b);
        m_windowZoomRect2 = addRect(m_windowZoomStart, 0,
                                   width() - m_windowZoomStart, height(), QPen(), b);
        m_windowZoomLine1->setZValue(11);
        m_windowZoomLine2->setZValue(11);
        m_windowZoomRect1->setZValue(10);
        m_windowZoomRect2->setZValue(10);

        event->accept();
    }
    else {
        QGraphicsScene::mousePressEvent(event);
    }
}

void GanttScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
    if (m_windowZoomMode) {

        int newPos = event->scenePos().x();
        int left = std::min(newPos, m_windowZoomStart);
        int right = std::max(newPos, m_windowZoomStart);

        m_windowZoomLine1->setLine(left, 0, left, height());
        m_windowZoomLine2->setLine(right, 0, right, height());

        m_windowZoomRect1->setRect(0, 0, left, height());
        m_windowZoomRect2->setRect(right, 0, width()-right, height());

        event->accept();
    }
    else {
        QGraphicsScene::mouseMoveEvent(event);
    }
}

void GanttScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) {
    if (m_windowZoomMode) {
        if (!m_windowZoomCancelled) {

            int newPos = event->scenePos().x();
            int left = std::min(newPos, m_windowZoomStart);
            int right = std::max(newPos, m_windowZoomStart);
            int ct = axis()->unit2px(m_cycle->length());
            float begScene = axis()->px2unit(left);
            float endScene = axis()->px2unit(std::max(right, ct));

            axis()->setScale(app()->ganttView()->viewport()->width()/axis()->px2unit(right-left));

            m_preferredSize.setWidth(axis()->unit2px(endScene) *1.1);
            app()->viewManager()->resizeScene(m_preferredSize);
            app()->viewManager()->scrollHorizontal(axis()->unit2px(begScene));

            m_windowZoomMode = false;
            app()->get_ui()->actionZoomWindow->setChecked(false);

            removeItem(m_windowZoomLine1);
            removeItem(m_windowZoomLine2);
            removeItem(m_windowZoomRect1);
            removeItem(m_windowZoomRect2);
            delete m_windowZoomLine1;
            delete m_windowZoomLine2;
            delete m_windowZoomRect1;
            delete m_windowZoomRect2;

            widthResize();
        }

        event->accept();
    }
    else {
        QGraphicsScene::mouseReleaseEvent(event);
    }
}



void GanttScene::keyReleaseEvent(QKeyEvent *event) {
    if (m_windowZoomMode && event->key() == Qt::Key_Escape) {
        removeItem(m_windowZoomLine1);
        removeItem(m_windowZoomLine2);
        removeItem(m_windowZoomRect1);
        removeItem(m_windowZoomRect2);
        delete m_windowZoomLine1;
        delete m_windowZoomLine2;
        delete m_windowZoomRect1;
        delete m_windowZoomRect2;
        m_windowZoomCancelled = true;
        event->accept();
    }
    else {
        QGraphicsScene::keyReleaseEvent(event);
    }
}


void GanttScene::widthResize() {
    m_preferredSize.setWidth(axis()->unit2px(m_cycle->length()) * 1.1);
    app()->viewManager()->resizeScene(m_preferredSize);

    int ct = axis()->unit2px(m_cycle->length());
    m_cycleTime->setLine(QLine(ct, 0, ct, height()));

    update();
}


void GanttScene::heightResize() {
    QRectF rc = m_ganttCycle->boundingRect();
    m_preferredSize.setHeight(rc.height() + m_app->rowHeight());
    app()->viewManager()->resizeScene(m_preferredSize);

    int ct = axis()->unit2px(m_cycle->length());
    m_cycleTime->setLine(QLine(ct, 0, ct, height()));
    update();
}




QColor GanttScene::colorForTask(PC::Task *task) {
	return QColor(colorStrForTask(task));
}


QString GanttScene::colorStrForTask(PC::Task *task) {
	QVariant v = task->property("color");
	if (v.isValid()) {
		return v.toString();
	}
	if (dynamic_cast<PC::AtomTask*>(task)) {
		return DEFAULT_ATASK_COLOR;
	}
	return DEFAULT_MTASK_COLOR;
}

// returns the same color with max saturation
QColor GanttScene::borderColor(const QColor &back) {
	return QColor::fromHsv(back.hue(), 255, back.value());
}
